use crate::{handler::DynEndpoint, http, http::StatusCode, request::Request, response::Response};
use route_recognizer::{Match, Params, Router as MethodRouter};
use std::collections::HashMap;

pub(crate) struct Selection<'a> {
    pub(crate) endpoint: &'a DynEndpoint,
    pub(crate) params: Params,
}

pub(crate) struct Router {
    method_map: HashMap<http::Method, MethodRouter<Box<DynEndpoint>>>,
}

impl Router {
    pub fn new() -> Self {
        Router {
            method_map: HashMap::default(),
        }
    }

    pub(crate) fn add(&mut self, path: &str, method: http_types::Method, ep: Box<DynEndpoint>) {
        self.method_map
            .entry(method)
            .or_insert_with(MethodRouter::new)
            .add(path, ep)
    }

    pub(crate) fn route(&self, path: &str, method: http::Method) -> Selection<'_> {
        if let Some(route_match) = self
            .method_map
            .get(&method)
            .and_then(|r| r.recognize(path).ok())
        {
            Selection {
                params: route_match.params().clone(),
                endpoint: (**route_match.handler()).as_ref(),
            }
        } else if self
            .method_map
            .iter()
            .filter(|(k, _)| **k != method)
            .any(|(_, r)| r.recognize(path).is_ok())
        {
            // If this `path` can be handled by a callback registered with a different HTTP method
            // should return 405 Method Not Allowed
            Selection {
                endpoint: &method_not_allowed,
                params: Params::new(),
            }
        } else {
            Selection {
                endpoint: &not_found_endpoint,
                params: Params::new(),
            }
        }
    }
}

async fn not_found_endpoint(_req: Request) -> crate::Result {
    Ok(Response::new(StatusCode::NotFound))
}

async fn method_not_allowed(_req: Request) -> crate::Result {
    Ok(Response::new(StatusCode::MethodNotAllowed))
}
